﻿using System;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;

using Microsoft.Practices.Composite.Events;
using Microsoft.Practices.Composite.Presentation.Commands;
using Microsoft.Practices.Composite.Presentation.Events;
using Microsoft.Xna.Framework.Graphics;

using Supremacy.Annotations;
using Supremacy.Client.Commands;
using Supremacy.Client.Events;
using Supremacy.Client.Views;
using Supremacy.Game;
using Supremacy.Types;
using Supremacy.Utility;
using Supremacy.Xna;

using Disposer = Supremacy.Types.Disposer;
using Expression = System.Linq.Expressions.Expression;
using Path = System.IO.Path;

namespace Supremacy.Client
{
    /// <summary>
    /// Interaction logic for ClientWindow.xaml
    /// </summary>
    public partial class ClientWindow : IGameWindow
    {
        private const double MaxUnscaledScreenWidth = 1600;

        public static readonly RoutedCommand ToggleFullScreenModeCommand = new RoutedCommand("ToggleFullScreenMode", typeof(ClientWindow));
        public static readonly RoutedCommand CollectGarbageCommand = new RoutedCommand("CollectGarbage", typeof(ClientWindow));

        private static readonly Func<double, double> PixelToPoint;

        private readonly IClientContext _clientContext;
        private readonly IEventAggregator _eventAggregator;
        private readonly INavigationCommandsProxy _navigationCommands;
        private readonly object _waitCursorLock;
        private readonly Cursor _defaultCursor;
        private readonly StateScope _settingsChangeScope;
        private readonly IAudioEngine _audioEngine;

        private int _waitCursorCount;
        private bool _isClosing;
        private bool _exitInProgress;
        private double _scaleFactor;

        static ClientWindow()
        {
            var convertPixelMethod = typeof(SystemParameters).GetMethod(
                "ConvertPixel",
                BindingFlags.Static | BindingFlags.NonPublic);

            var pixelParameter = Expression.Parameter(typeof(double), "pixel");

            PixelToPoint = Expression.Lambda<Func<double, double>>(
                Expression.Call(
                    convertPixelMethod,
                    Expression.Convert(pixelParameter, typeof(int))),
                pixelParameter).Compile();
        }

        public ClientWindow(
            [NotNull] IClientContext clientContext,
            [NotNull] IAudioEngine audioEngine,
            [NotNull] IEventAggregator eventAggregator,
            [NotNull] INavigationCommandsProxy navigationCommands)
        {
            if (clientContext == null)
                throw new ArgumentNullException("clientContext");
            if (eventAggregator == null)
                throw new ArgumentNullException("eventAggregator");
            if (navigationCommands == null)
                throw new ArgumentNullException("navigationCommands");

            _clientContext = clientContext;
            _audioEngine = audioEngine;
            _eventAggregator = eventAggregator;
            _navigationCommands = navigationCommands;
            _waitCursorLock = new object();
            _settingsChangeScope = new StateScope();

            _defaultCursor = new Cursor(
                Path.Combine(
                    Environment.CurrentDirectory,
                    @"Resources\Cursors\cursor.cur"));

            InitializeComponent();

            /*
             * Officially, we only support video resolutions of 1024x768 and up.  However, considering
             * 1280x720 is one of the standard High Definition resolutions, we will adjust our minimum
             * size constraints to accomodate it.
             */
            if ((SystemParameters.PrimaryScreenWidth == 1280) &&
                (SystemParameters.PrimaryScreenHeight == 720))
            {
                this.MinHeight = 720;
                this.Height = 720;
            }

            this.Cursor = _defaultCursor;

            this.Loaded += OnLoaded;
            this.SizeChanged += OnSizeChanged;

            ClientCommands.ToggleConsole.RegisterCommand(
                new DelegateCommand<object>(ExecuteToggleConsole));

            _eventAggregator.GetEvent<GameStartedEvent>().Subscribe(OnGameStarted, ThreadOption.UIThread);
            _eventAggregator.GetEvent<GameEndedEvent>().Subscribe(OnGameEnded, ThreadOption.UIThread);
            _eventAggregator.GetEvent<GameEndingEvent>().Subscribe(OnGameEnding, ThreadOption.UIThread);
            _eventAggregator.GetEvent<ClientDisconnectedEvent>().Subscribe(OnClientDisconnected, ThreadOption.UIThread);
            _eventAggregator.GetEvent<GameEndedEvent>().Subscribe(OnGameEnded, ThreadOption.UIThread);
            _eventAggregator.GetEvent<ViewActivatingEvent>().Subscribe(OnViewActivating, ThreadOption.PublisherThread);

            this.ModelessDialogsRegion.SelectionChanged += OnModelessDialogsRegionSelectionChanged;
            this.ModalDialogsRegion.SelectionChanged += OnModalDialogsRegionSelectionChanged;

            ClientSettings.Current.EnableAntiAliasingChanged += OnEnableAntiAliasingSettingsChanged;

            this.ApplyAntiAliasingSettings();

            this.InputBindings.Add(
                new KeyBinding(
                    ClientCommands.ToggleConsole,
                    new KeyGesture(Key.OemTilde, ModifierKeys.Control)));

            this.InputBindings.Add(
                new KeyBinding(
                    CollectGarbageCommand,
                    new KeyGesture(Key.G, ModifierKeys.Control | ModifierKeys.Shift)));

            this.InputBindings.Add(
                new KeyBinding(
                    ClientCommands.EscapeCommand,
                    new KeyGesture(Key.Escape, ModifierKeys.None)));

            this.InputBindings.Add(
                new KeyBinding(
                    _navigationCommands.ActivateScreen,
                    new KeyGesture(Key.F1, ModifierKeys.None))
                {
                    CommandParameter = StandardGameScreens.GalaxyScreen
                });

            this.InputBindings.Add(
                new KeyBinding(
                    _navigationCommands.ActivateScreen,
                    new KeyGesture(Key.F2, ModifierKeys.None))
                {
                    CommandParameter = StandardGameScreens.ColonyScreen
                });

            this.InputBindings.Add(
                new KeyBinding(
                    _navigationCommands.ActivateScreen,
                    new KeyGesture(Key.F3, ModifierKeys.None))
                {
                    CommandParameter = StandardGameScreens.DiplomacyScreen
                });

            this.InputBindings.Add(
                new KeyBinding(
                    _navigationCommands.ActivateScreen,
                    new KeyGesture(Key.F4, ModifierKeys.None))
                {
                    CommandParameter = StandardGameScreens.ScienceScreen
                });

            this.InputBindings.Add(
                new KeyBinding(
                    _navigationCommands.ActivateScreen,
                    new KeyGesture(Key.F5, ModifierKeys.None))
                {
                    CommandParameter = StandardGameScreens.PersonnelScreen
                });

            this.InputBindings.Add(
                new KeyBinding(
                    ToggleFullScreenModeCommand,
                    Key.Enter,
                    ModifierKeys.Alt));

            this.CommandBindings.Add(
                new CommandBinding(
                    ClientCommands.EscapeCommand,
                    ExecuteEscapeCommand));

            this.CommandBindings.Add(
                new CommandBinding(
                    ToggleFullScreenModeCommand,
                    (s, e) => ToggleFullScreenMode()));

            this.CommandBindings.Add(
                new CommandBinding(
                    CollectGarbageCommand,
                    (s, e) =>
                    {
                        GameLog.Client.General.Info("Forcing garbage collection...");
                        GC.Collect();
                        GC.WaitForPendingFinalizers();
                    }));

            var settings = ClientSettings.Current;

            this.Width = settings.ClientWindowWidth;
            this.Height = settings.ClientWindowHeight;

            CheckClientSettings();
        }

        private void OnEnableAntiAliasingSettingsChanged(object sender, PropertyChangedRoutedEventArgs<bool> args)
        {
            if (this.Dispatcher.CheckAccess())
                this.ApplyAntiAliasingSettings();
            else
                this.Dispatcher.Invoke(DispatcherPriority.Send, (Action)this.ApplyAntiAliasingSettings);
        }

        private void ApplyAntiAliasingSettings()
        {
            if (ClientSettings.Current.EnableAntiAliasing)
                ContentPanel.ClearValue(RenderOptions.EdgeModeProperty);
            else
                RenderOptions.SetEdgeMode(ContentPanel, EdgeMode.Aliased);
        }

        private void ExecuteToggleConsole(object o)
        {
            if (this.Console.Visibility == Visibility.Visible)
                this.Console.Visibility = Visibility.Hidden;
            else
                this.Console.Visibility = Visibility.Visible;
        }

        private void OnSizeChanged(object sender, SizeChangedEventArgs e)
        {
            using (_settingsChangeScope.Enter())
            {
                SaveWindowDimensions();
            }
        }

        private void SaveWindowDimensions()
        {
            if (this.RestoreBounds.IsEmpty)
                return;
            ClientSettings.Current.ClientWindowWidth = this.RestoreBounds.Width;
            ClientSettings.Current.ClientWindowHeight = this.RestoreBounds.Height;
            ClientSettings.Current.Save();
        }

        private void CheckClientSettings()
        {
            if (_settingsChangeScope.IsWithin)
                return;

            RenderOptions.SetBitmapScalingMode(
                this.ContentPanel,
                ClientSettings.Current.EnableHighQualityScaling
                    ? BitmapScalingMode.HighQuality
                    : BitmapScalingMode.LowQuality);

            SetFullScreenMode(ClientSettings.Current.EnableFullScreenMode);
        }

        private void OnViewActivating(ViewActivatingEventArgs e)
        {
            if (!_clientContext.IsConnected)
                return;
            e.Cancel = IsDialogOpen();
        }

        private void OnLoaded(object @object, RoutedEventArgs routedEventArgs)
        {
            this.Loaded -= OnLoaded;

            ClientSettings.Current.Saved += (s, e) => CheckClientSettings();
            ClientSettings.Current.Loaded += (s, e) => CheckClientSettings();

#if !NOMUSIC
            if (ClientApp.CmdLineArgs.NoMusic)
                return;

            _audioEngine.LoadMusic(MusicPack.LoadDefault());
            _audioEngine.MaxVolume = (float)ClientSettings.Current.MusicVolume;
            ClientSettings.Current.MusicVolumeChanged += (s, e) => _audioEngine.MaxVolume = (float)e.NewValue;
            _audioEngine.Start();

            ClientEvents.ScreenActivated.Subscribe(e => _audioEngine.SwitchMusicTrack(e.ScreenName));
#endif
        }

        private void OnModelessDialogsRegionSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            CheckScreenFocus();
        }

        private bool IsDialogOpen()
        {
            return ((this.ModalDialogsRegion.ActiveDialog != null) || (this.ModelessDialogsRegion.ActiveDialog != null));
        }

        private void CheckScreenFocus()
        {
            if (IsDialogOpen())
                return;
            var currentScreen = this.GameScreensRegion.CurrentScreen;
            if (currentScreen != null)
                currentScreen.Focus();
            else
                this.GameScreensRegion.Focus();
        }

        private void ExecuteEscapeCommand(object sender, ExecutedRoutedEventArgs args)
        {
            if (IsDialogOpen())
                return;

            if (GameScreensRegion.CurrentScreen is IGalaxyScreenView)
                _navigationCommands.ActivateScreen.Execute(StandardGameScreens.MenuScreen);
            else if (!(GameScreensRegion.CurrentScreen is MenuScreen))
                _navigationCommands.ActivateScreen.Execute(StandardGameScreens.GalaxyScreen);
        }

        protected void ToggleFullScreenMode()
        {
            SetFullScreenMode(this.WindowState != WindowState.Maximized);
        }

        protected override Size MeasureOverride(Size availableSize)
        {
            if (_settingsChangeScope.IsWithin)
                return availableSize;
            return base.MeasureOverride(availableSize);
        }

        protected override Size ArrangeOverride(Size arrangeBounds)
        {
            if (_settingsChangeScope.IsWithin)
                return arrangeBounds;
            return base.ArrangeOverride(arrangeBounds);
        }

        protected void SetFullScreenMode(bool enableFullScreenMode)
        {
            using (_settingsChangeScope.Enter())
            {
                using (Dispatcher.DisableProcessing())
                {
                    if (enableFullScreenMode)
                    {
                        if (this.WindowState == WindowState.Maximized)
                            return;
                        this.ResizeMode = ResizeMode.CanMinimize;
                        this.WindowStyle = WindowStyle.None;
                        this.WindowState = WindowState.Maximized;
                        ClientSettings.Current.EnableFullScreenMode = true;
                        ClientSettings.Current.Save();
                    }
                    else
                    {
                        if (this.WindowState == WindowState.Normal)
                            return;
                        this.CenterToScreen();
                        this.ResizeMode = ResizeMode.CanResize;
                        this.WindowStyle = WindowStyle.SingleBorderWindow;
                        this.WindowState = WindowState.Normal;
                        ClientSettings.Current.EnableFullScreenMode = false;
                        ClientSettings.Current.Save();
                    }
                }
            }
            InvalidateMeasure();
        }

        protected void CenterToScreen()
        {
            var workArea = SystemParameters.WorkArea;
            var windowSize = this.RestoreBounds;

            if (double.IsInfinity(windowSize.Width) || double.IsInfinity(windowSize.Height))
                windowSize = new Rect(0, 0, this.Width, this.Height);

            this.Left = (workArea.Width - windowSize.Width) / 2;
            this.Top = (workArea.Height - windowSize.Height) / 2;
        }

        protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
        {
            base.OnRenderSizeChanged(sizeInfo);

            if (sizeInfo.NewSize.Width > MaxUnscaledScreenWidth)
            {
                _scaleFactor = (sizeInfo.NewSize.Width / MaxUnscaledScreenWidth);

                ClientProperties.SetScaleFactor(this, _scaleFactor);
                
                this.LayoutTransform = this.ContentPanel.LayoutTransform = new ScaleTransform(_scaleFactor, _scaleFactor, 0.5, 0.5);
                
                //TextOptions.SetTextFormattingMode(
                //    this.ContentPanel,
                //    TextFormattingMode.Ideal);
            }
            else
            {
                _scaleFactor = 1.0;

                ClearValue(ClientProperties.ScaleFactorProperty);

                this.LayoutTransform = this.ContentPanel.LayoutTransform = null;

                //TextOptions.SetTextFormattingMode(
                //    this.ContentPanel,
                //    TextFormattingMode.Display);
            }
        }

        private void OnModalDialogsRegionSelectionChanged(object sender, SelectionChangedEventArgs args)
        {
            var enableShaders = RenderCapability.IsPixelShaderVersionSupported(2, 0);

            if (this.ModalDialogsRegion.ActiveDialog == null)
            {
                this.GameScreensRegionBorder.Opacity = 1.0;
                //this.GameScreensRegionBorder.Effect = null;
                CheckScreenFocus();

/*
                var currentScreen = this.GameScreensRegion.CurrentScreen as IAnimationsHost;
                if (currentScreen != null)
                    currentScreen.ResumeAnimations();
*/
            }
            else if (enableShaders)
            {
/*
                var currentScreen = this.GameScreensRegion.CurrentScreen as IAnimationsHost;
                if (currentScreen != null)
                    currentScreen.PauseAnimations();
*/

                this.GameScreensRegionBorder.Opacity = 0.4;
/*
                this.GameScreensRegionBorder.Effect = new ColorToneEffect
                                                      {
                                                          DarkColor = Color.FromScRgb(1.0f, 0.4f, 0.3f, 0.5f),
                                                          LightColor = Color.FromScRgb(1.0f, 0.4f, 0.3f, 0.5f),
                                                          Desaturation = 0.80,
                                                          Toned = 0.0
                                                      };
*/
            }
            else
            {
                this.GameScreensRegionBorder.Effect = null;
            }
        }

        private void OnClientDisconnected(ClientDataEventArgs<ClientDisconnectReason> obj)
        {
            if (_isClosing)
                _exitInProgress = true;
        }

        private void OnGameEnding(ClientEventArgs obj)
        {
            if (_isClosing)
                _exitInProgress = true;
        }

        private void OnGameEnded(ClientEventArgs obj)
        {
            if (_isClosing)
                _exitInProgress = true;
            this.ClearValue(ContextMenuProperty);
        }

        private void OnGameStarted(ClientDataEventArgs<GameStartData> obj)
        {
            this.ContextMenu = new GameContextMenu { CustomPopupPlacementCallback = ContextMenuPlacementCallback };
        }

        protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
        {
            base.OnClosing(e);

            if (e.Cancel)
                return;

            _isClosing = true;

            try
            {
                ClientCommands.Exit.Execute(true);
            }
            catch
            {
                _exitInProgress = true;
            }

            if (_exitInProgress)
                return;

            _isClosing = false;
            e.Cancel = true;
        }

        protected override void OnClosed(EventArgs e)
        {
            base.OnClosed(e);

            SaveWindowDimensions();
            ClientSettings.Current.Save();
#if !NOMUSIC
            if (!ClientApp.CmdLineArgs.NoMusic)
                _audioEngine.Dispose();
#endif
        }

        private CustomPopupPlacement[] ContextMenuPlacementCallback(
            Size popupSize,
            // ReSharper disable RedundantAssignment
            Size targetSize,
            // ReSharper restore RedundantAssignment
            Point offset)
        {
            var dpiConversion = PixelToPoint(1.0);
            var mouse = Mouse.GetPosition(this);

            if (this.ContextMenu != null)
                popupSize = (Size)((Vector)this.ContextMenu.RenderSize * _scaleFactor);

            targetSize = (Size)((Vector)this.ContentPanel.RenderSize * _scaleFactor);

            var point = new Point(
                Math.Max(
                    0,
                    Math.Min(
                        mouse.X - popupSize.Width / 2,
                        targetSize.Width - popupSize.Width)) / dpiConversion,
                Math.Max(
                    0,
                    Math.Min(
                        mouse.Y - popupSize.Height / 2,
                        targetSize.Height - popupSize.Height)) / dpiConversion);

            return new[] { new CustomPopupPlacement { Point = point } };
        }

        public void ForceWaitCursor()
        {
            lock (_waitCursorLock)
            {
                _waitCursorCount++;
                this.Cursor = Cursors.Wait;
            }
            Mouse.UpdateCursor();
        }

        public void ClearWaitCursor()
        {
            lock (_waitCursorLock)
            {
                if (_waitCursorCount > 0)
                    _waitCursorCount--;
                if (_waitCursorCount == 0)
                    this.Cursor = _defaultCursor;
            }
            Mouse.UpdateCursor();
        }

        #region Implementation of IGameWindow
        public IDisposable EnterWaitCursorScope()
        {
            ForceWaitCursor();
            return new Disposer(ClearWaitCursor);
        }
        #endregion
    }
}
